package pl.shenlon.ga;

import java.lang.reflect.Array;
import java.util.Collections;

import pl.shenlon.ga.evaluation.LinearPopulationEvaluator;
import pl.shenlon.ga.evaluation.PopulationEvaluator;
import pl.shenlon.ga.selection.PopulationSpecimenSelector;
import pl.shenlon.ga.selection.RankedSpecimenSelector;
import pl.shenlon.poker.game.TestHand;

@SuppressWarnings("unchecked")
public class Population<T extends Specimen> {

	private int cash;

	private int elitarismRate;

	private PopulationEvaluator evaluator;

	private final T[] fittestSpecimen;

	private double mutationRate, crossoverRate, survivalRate;

	private final T[] population;

	private int populationScore;

	private PopulationSpecimenSelector selector;

	public final int size;

	private final Class<T> specimenClass;

	{
		selector = new RankedSpecimenSelector(this);
	}

	public Population(int size, Class<T> specimenClass) {
		this.specimenClass = specimenClass;
		population = (T[]) Array.newInstance(specimenClass, size);
		for (int i = 0; i < size; i++) {
			try {
				population[i] = specimenClass.newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		this.size = size;
		elitarismRate = 1;
		crossoverRate = 0.6;
		survivalRate = 0.5;
		mutationRate = 0.4;
		cash = 100;
		fittestSpecimen = (T[]) Array.newInstance(specimenClass, elitarismRate);
		evaluator = new LinearPopulationEvaluator(this);
	}

	public Population(Population<T> population, double survivalRate) {
		this.specimenClass = population.specimenClass;
		this.population = (T[]) Array.newInstance(specimenClass,
				population.size);
		this.fittestSpecimen = (T[]) Array.newInstance(specimenClass,
				population.elitarismRate);
		this.size = population.size;
		this.elitarismRate = population.elitarismRate;
		this.crossoverRate = population.crossoverRate;
		this.survivalRate = population.survivalRate;
		this.mutationRate = population.mutationRate;
		this.cash = population.cash;
		int splitPosition = (int) (size * survivalRate);
		for (int i = 0; i < splitPosition; i++) {
			this.population[i] = (T) population.population[i].copy();
		}
		for (int i = splitPosition; i < size; i++) {
			try {
				this.population[i] = specimenClass.newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		this.evaluator = population.evaluator;
	}

	public Population(Population<T> population, double survivalRate, int size) {
		this.specimenClass = population.specimenClass;
		this.population = (T[]) Array.newInstance(specimenClass, size);
		this.fittestSpecimen = (T[]) Array.newInstance(specimenClass,
				population.elitarismRate);
		this.elitarismRate = population.elitarismRate;
		this.crossoverRate = population.crossoverRate;
		this.survivalRate = population.survivalRate;
		this.mutationRate = population.mutationRate;
		this.cash = population.cash;
		this.size = size;
		if (size >= population.size) {
			int splitPosition = (int) (population.size * survivalRate);
			for (int i = 0; i < splitPosition; i++) {
				this.population[i] = (T) population.population[i].copy();
			}
			for (int i = splitPosition; i < size; i++) {
				try {
					this.population[i] = specimenClass.newInstance();
				} catch (InstantiationException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		} else if (size < population.size) {
			int splitPosition = (int) (size * survivalRate);
			for (int i = 0; i < splitPosition; i++) {
				this.population[i] = (T) population.population[i].copy();
			}
			for (int i = splitPosition; i < size; i++) {
				try {
					this.population[i] = specimenClass.newInstance();
				} catch (InstantiationException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		this.evaluator = population.evaluator;
	}

	public void breed() {
		evaluatePopulation();
		sortDescending();
		prepareElitaristSpecimens();
		crossover();
		evaluatePopulation();
		restoreElitarismSpecimens();
		sortDescending();
	}

	public void crossover() {
		Specimen[] selectedSpecimens = selector.select();
		for (int i = 0; i < elitarismRate; i++) {
			selectedSpecimens[i] = fittestSpecimen[i].copy();
		}

		for (int i = (int) (size * survivalRate); i < size; i++) {
			try {
				selectedSpecimens[i] = specimenClass.newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		selectedSpecimens = Specimen.shuffleSpecimens(selectedSpecimens);
		for (int i = 0; i < size; i += 2) {
			Specimen[] childrens = selectedSpecimens[i].crossover(
					crossoverRate, selectedSpecimens[i + 1]);
			for (Specimen children : childrens) {
				children.mutate(mutationRate);
			}
			selectedSpecimens[i] = childrens[0];
			selectedSpecimens[i + 1] = childrens[1];
		}

		for (int i = 0; i < size; i++) {
			population[i] = (T) selectedSpecimens[i].copy();
		}

		// evaluatePopulation();
		// sortDescending();
		//
		// for (int i = 0; i < elitarismRate; i++) {
		// if (population[i].compareTo(fittestSpecimen[i]) < 0) {
		// population[i] = (T) fittestSpecimen[i].copy();
		// }
		// }
	}
	public void evaluatePopulation() {
		populationScore = 0;
		evaluator.setPopulation(this);
		for (Specimen specimen : population) {
			specimen.setScore(0);
		}
		for (TestHand hand : PopulationEvaluator.TEST_HANDS) {
			evaluator.evaluatePopulation(hand);
		}
		for (Specimen specimen : population) {
			populationScore += specimen.getScore();
		}
	}

	public int getCash() {
		return cash;
	}

	public double getCrossoverRate() {
		return crossoverRate;
	}

	public int getElitarismRate() {
		return elitarismRate;
	}

	public PopulationEvaluator getEvaluator() {
		return evaluator;
	}

	public T[] getFittestSpecimen() {
		return fittestSpecimen;
	}

	public double getMutationRate() {
		return mutationRate;
	}

	public T[] getPopulation() {
		return population;
	}

	public int getPopulationSize() {
		return size;
	}

	public int getScore() {
		return populationScore;
	}

	public PopulationSpecimenSelector getSelector() {
		return selector;
	}

	public T getSpecimen(int index) {
		return population[index];
	}

	public Class<T> getSpecimenClass() {
		return specimenClass;
	}

	public double getSurvivalRate() {
		return survivalRate;
	}

	private void prepareElitaristSpecimens() {
		for (int i = 0; i < elitarismRate; i++) {
			fittestSpecimen[i] = (T) population[i].copy();
			fittestSpecimen[i].setScore(population[i].getScore());
		}
	}

	private void restoreElitarismSpecimens() {
		for (int i = 0; i < elitarismRate; i++) {
			if (population[i].compareTo(fittestSpecimen[i]) < 0) {
				population[i] = (T) fittestSpecimen[i].copy();
				population[i].setScore(fittestSpecimen[i].getScore());
			}
		}
	}

	public void setCash(int cash) {
		this.cash = cash;
	}

	public void setCrossoverRate(double crossoverRate) {
		this.crossoverRate = crossoverRate;
	}

	public void setElitarismRate(int elitarismRate) {
		this.elitarismRate = elitarismRate;
		prepareElitaristSpecimens();
	}

	public void setEvaluator(PopulationEvaluator evaluator) {
		this.evaluator = evaluator;
	}

	public void setMutationRate(double mutationRate) {
		this.mutationRate = mutationRate;
	}

	public void setSelector(PopulationSpecimenSelector selector) {
		this.selector = selector;
	}

	public void setSpecimen(int index, T specimen) {
		population[index] = (T) specimen.copy();
	}

	public void setSurvivalRate(double survivalRate) {
		this.survivalRate = survivalRate;
	}

	public void sortAscending() {
		java.util.Arrays.sort(population);
	}

	public void sortDescending() {
		java.util.Arrays.sort(population, Collections.reverseOrder());
	}
}